In contrast to C printf-like functions, C++ provides safer and more robust interfaces for performing text formatting:
- The
std::format
interface family (C++20) allows formatting text into a string.
- The
std::print
interface family (C++23) allows printing formatted text.
C++ formatting facilities perform validation of the format string against the type of the formatted argument. If the validation fails, it is
reported as a compilation error for the calls of std::print
and std::format
. When the format string is not available at
compile-time, std::vformat
, std::vprint_unicode
, and std::vprint_nonunicode
can be used. They will report
failures at runtime by throwing an instance of std::format_error
.
Secondly, the relation between the type and format specifier is more abstract. In particular, {:d}
can be used to format any integer
type, regardless of its size and signedness. Similarly, {:f}
works for any floating point type. Furthermore, {}
can be used
for any type with default format spec, which makes it usable in the generic context.
Finally, the text formatting API was designed with adaptability in mind:
- Formatting of user-defined types is possible with the dedicated format specification via
std::formatter
specializations.
- The string formatting API provides functions for:
- receiving the formatted text by return -
std::format
.
- writing the formatted text to an output iterator -
std::format_to
.
- The
std::print
API provides function overloads for:
- printing implicitly to the standard output.
- printing to a
FILE*
handle.
- printing to a
std::ostream&
object.
This rule raises issues for calls of the printf
, fprintf
, sprintf
and snprintf
functions that
can be replaced by the C++ formatting functions.
Noncompliant code example
void printTextIntoBuffer(char* out) {
// Assumes the buffer pointed-to by out is large enough
sprintf(out, "%u %s", 10u, "text"); // Noncompliant
}
void printTextIntoSizedBuffer(char* out, size_t n) {
std::snprintf(out, n, "%i %% %LG", 10, 10.0L); // Noncompliant
}
void printToFile(FILE* f) {
printf("%i", 10); // Noncompliant since C++23
std::fprintf(f, "%f", 10.0); // Noncompliant since C++23
}
Compliant solution
void printTextIntoBuffer(char* out) {
// Assumes the buffer pointed-to by out is large enough
std::format_to(out, "{} {}", 10u, "text"); // Compliant
}
// The function can also be redesigned to deal with memory allocation
// and return a string:
std::string getText() {
return std::format("{} {}", 10u, "text"); // Compliant
}
void printTextIntoSizedBuffer(char* out, size_t n) {
std::format_to_n(out, n, "{} % {:G}", 10, 10.0L); // Compliant
}
void printToFile(FILE* f) {
std::print("{}", 10); // Compliant
std::print(f, "{}", 10.0); // Compliant
}
Exceptions
The rule does not raise an issue if the format string passed to a printf-like function is computed dynamically instead of being spelled in the
source code:
char const* localizedFormatString(unsigned id);
/* …. */
snprintf(buffer, localizedFormatString(123), 10, 20)
While std::vformat
may be used in such cases, it requires changing the format string, which may not be actionable.